﻿using System;
using System.Collections.Generic;
using System.Drawing; 

namespace ICSharpCode.WinFormsUI.Controls.Chart3D
{
    public class Coordinate3D
    {       
        private ViewPort viewPort = null;
        private Vertex[] pts3d = new Vertex[8];
        public Vertex[] Vertexes
        {
            get { return pts3d; }
        }

        private float d = 100; 
        private float h = 100;
        public SizeF Range
        {
            get { return new SizeF(d, h); }
            set
            {
                SizeF sizef = value;
                d = sizef.Width;
                h = sizef.Height;
                InitializeCoordinate();
                if (viewPort != null && viewPort.Chart != null)
                    viewPort.Chart.Invalidate();
            }
        }

        private Axis _xaxis = new Axis();
        public Axis Xaxis
        {
            get { return _xaxis; }
        }

        private Axis _yaxis = new Axis();
        public Axis Yaxis
        {
            get { return _yaxis; }
        }

        private Axis _zaxis = new Axis();
        public Axis Zaxis
        {
            get { return _zaxis; }
        }

        private Pen linePen = new Pen(SystemColors.ControlDark, 1.0F);
        public Pen LinePen
        {
            get { return linePen; }
            set { linePen = value; }
        }

        private bool fillingFace = true;
        public bool FillingFace
        {
            get { return fillingFace; }
            set { fillingFace = value; }
        }

        private bool scaleIsVisable = false;
        public bool ScaleIsVisable
        {
            get { return scaleIsVisable; }
            set
            {
                scaleIsVisable = value;
                if (viewPort != null && viewPort.Chart != null)
                {
                    viewPort.Chart.Invalidate();
                }
            }
        }

        private StringFormat legendStringFormat = null;

        private Font font = new Font("宋体", 9.0f);
        public Font Font
        {
            get { return font; }
            set { font = value; }
        }

        public Coordinate3D()
        {
            InitializeCoordinate();
            legendStringFormat = new StringFormat() { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Center };
        }

        public Coordinate3D(ViewPort viewPort)
            : this()
        {
            this.viewPort = viewPort;
        }

        private void InitializeCoordinate()
        {
            pts3d[0] = new Vertex(-d, -d, h);
            pts3d[1] = new Vertex(d, -d, h);
            pts3d[2] = new Vertex(d, -d, 0);
            pts3d[3] = new Vertex(-d, -d, 0);
            pts3d[4] = new Vertex(-d, d, h);
            pts3d[5] = new Vertex(d, d, h);
            pts3d[6] = new Vertex(d, d, 0);
            pts3d[7] = new Vertex(-d, d, 0);
        }

        private void DrawFaces(Graphics g)
        {
            Point2D[] pts2d = viewPort.Camera.GetProjection(pts3d);

            if (!scaleIsVisable)
            {
                if (fillingFace)
                {
                    g.DrawString("(0,0)", this.Font, Brushes.Black, pts2d[3].Offset(-30, 0).ToPointF());
                    g.DrawString("X", this.Font, Brushes.Black, pts2d[2].Offset(0, 0).ToPointF());
                    g.DrawString("Y", this.Font, Brushes.Black, pts2d[0].Offset(-6, -20).ToPointF());
                    g.DrawString("Z", this.Font, Brushes.Black, pts2d[6].Offset(0, 0).ToPointF());
                }
            }

            PointF[][] face = new PointF[6][];
            face[0] = new PointF[] { pts2d[0].ToPointF(), pts2d[1].ToPointF(), pts2d[2].ToPointF(), pts2d[3].ToPointF() };
            face[1] = new PointF[] { pts2d[5].ToPointF(), pts2d[1].ToPointF(), pts2d[0].ToPointF(), pts2d[4].ToPointF() };
            face[2] = new PointF[] { pts2d[1].ToPointF(), pts2d[5].ToPointF(), pts2d[6].ToPointF(), pts2d[2].ToPointF() };
            face[3] = new PointF[] { pts2d[2].ToPointF(), pts2d[6].ToPointF(), pts2d[7].ToPointF(), pts2d[3].ToPointF() };
            face[4] = new PointF[] { pts2d[3].ToPointF(), pts2d[7].ToPointF(), pts2d[4].ToPointF(), pts2d[0].ToPointF() };
            face[5] = new PointF[] { pts2d[4].ToPointF(), pts2d[7].ToPointF(), pts2d[6].ToPointF(), pts2d[5].ToPointF() };

            if (fillingFace)
            {
                using (var brush = new SolidBrush(Color.WhiteSmoke))
                {
                    for (int i = 3; i < 6; i++)
                    {
                        if (!Vector2D.IsClockwise(face[i][0], face[i][1], face[i][2]))
                        {
                            g.FillPolygon(brush, face[i]);
                        }
                    }
                }

                g.DrawLine(linePen, pts2d[0].X, pts2d[0].Y, pts2d[3].X, pts2d[3].Y);
                g.DrawLine(linePen, pts2d[0].X, pts2d[0].Y, pts2d[4].X, pts2d[4].Y);
                g.DrawLine(linePen, pts2d[2].X, pts2d[2].Y, pts2d[3].X, pts2d[3].Y);
                g.DrawLine(linePen, pts2d[2].X, pts2d[2].Y, pts2d[6].X, pts2d[6].Y);
                g.DrawLine(linePen, pts2d[3].X, pts2d[3].Y, pts2d[7].X, pts2d[7].Y);
                g.DrawLine(linePen, pts2d[4].X, pts2d[4].Y, pts2d[7].X, pts2d[7].Y);
                g.DrawLine(linePen, pts2d[4].X, pts2d[4].Y, pts2d[5].X, pts2d[5].Y);
                g.DrawLine(linePen, pts2d[5].X, pts2d[5].Y, pts2d[6].X, pts2d[6].Y);
                g.DrawLine(linePen, pts2d[6].X, pts2d[6].Y, pts2d[7].X, pts2d[7].Y);
            }
            else
            {
                using (var brush = new SolidBrush(Color.WhiteSmoke))
                {
                    for (int i = 0; i < 6; i++)
                    {
                        if (!Vector2D.IsClockwise(face[i][0], face[i][1], face[i][2]))
                        {
                            g.DrawPolygon(linePen, face[i]);
                        }
                    }
                }                 
            }
            
        }

        private void DrawScales(Graphics g)
        {
            if (!fillingFace)
                return;

            if (scaleIsVisable)
            {
                int d_scale = 16;

                Pen scale_pen = new Pen(Color.Black, 1.0f);

                Vertex scale_pt1 = pts3d[3].Offset(0, -d_scale, -d_scale);
                Vertex scale_pt2 = pts3d[2].Offset(0, -d_scale, -d_scale);

                Vertex scale_pt3 = pts3d[2].Offset(d_scale, 0, -d_scale);
                Vertex scale_pt4 = pts3d[6].Offset(d_scale, 0, -d_scale);

                Vertex scale_pt5 = pts3d[3].Offset(-d_scale, -d_scale, 0);
                Vertex scale_pt6 = pts3d[0].Offset(-d_scale, -d_scale, 0);

                Point2D[] scale_pts = viewPort.Camera.GetProjection(new Vertex[] { 
                    scale_pt1, pts3d[3],
                    scale_pt2, pts3d[2],                   
                    scale_pt5, pts3d[3],
                    scale_pt6, pts3d[0],
                    scale_pt3, pts3d[2],
                    scale_pt4, pts3d[6]
                });

                d_scale = 24;

                Vertex label_pt1 = pts3d[3].Offset(0, -d_scale, -d_scale);
                Vertex label_pt2 = pts3d[3].Offset(0, -d_scale, -d_scale);

                Vertex label_pt3 = pts3d[2].Offset(d_scale, 0, -d_scale);
                Vertex label_pt4 = pts3d[2].Offset(d_scale, 0, -d_scale);

                Vertex label_pt5 = pts3d[3].Offset(-d_scale, -d_scale, 0);
                Vertex label_pt6 = pts3d[3].Offset(-d_scale, -d_scale, 0);

                Point2D[] label_pts = viewPort.Camera.GetProjection(new Vertex[] { 
                    label_pt1, pts3d[3],
                    label_pt2, pts3d[2].Offset(0, -d_scale, -d_scale),                   
                    label_pt5, pts3d[3],
                    label_pt6, pts3d[0].Offset(-d_scale, -d_scale, 0),
                    label_pt3, pts3d[2],
                    label_pt4, pts3d[6].Offset(d_scale, 0, -d_scale)
                });


                if (Xaxis.IsVisable)
                {
                    for (int i = 0; i < 4; i += 2)
                    {
                        g.DrawLine(scale_pen, scale_pts[i].X, scale_pts[i].Y, scale_pts[i + 1].X, scale_pts[i + 1].Y);
                    }

                    string xaxis_label = "-" + Xaxis.ScaleFormat.ToString();// Xaxis.Min + "";
                    if (Xaxis.Min >= 0) xaxis_label = "0";
                    SizeF xsizef = g.MeasureString(xaxis_label, this.font);
                    g.DrawString(xaxis_label, this.font, Brushes.Black, label_pts[2].X - xsizef.Width / 2, label_pts[2].Y + xsizef.Height / 2);

                    xaxis_label = Xaxis.ScaleFormat.ToString(); //Xaxis.Max + "";
                    xsizef = g.MeasureString(xaxis_label, this.font);
                    g.DrawString(xaxis_label, this.font, Brushes.Black, label_pts[3].X - xsizef.Width / 2, label_pts[3].Y + xsizef.Height / 2);

                }

                if (Yaxis.IsVisable)
                {
                    for (int i = 4; i < 8; i += 2)
                    {
                        g.DrawLine(scale_pen, scale_pts[i].X, scale_pts[i].Y, scale_pts[i + 1].X, scale_pts[i + 1].Y);
                    }

                    string yaxis_label = "-" + Yaxis.ScaleFormat.ToString(); //Yaxis.Min + "";
                    if (Yaxis.Min >= 0) yaxis_label = "0";
                    SizeF ysizef = g.MeasureString(yaxis_label, this.font);
                    g.DrawString(yaxis_label, this.font, Brushes.Black, label_pts[6].X - ysizef.Width, label_pts[6].Y - ysizef.Height / 2);

                    yaxis_label = Yaxis.ScaleFormat.ToString(); //Yaxis.Max + "";
                    ysizef = g.MeasureString(yaxis_label, this.font);
                    g.DrawString(yaxis_label, this.font, Brushes.Black, label_pts[7].X - ysizef.Width, label_pts[7].Y - ysizef.Height / 2);
                }
                
                if (Zaxis.IsVisable)
                {
                    for (int i = 8; i < 12; i += 2)
                    {
                        g.DrawLine(scale_pen, scale_pts[i].X, scale_pts[i].Y, scale_pts[i + 1].X, scale_pts[i + 1].Y);
                    }

                    string zaxis_label = "-" + Zaxis.ScaleFormat.ToString(); //Zaxis.Min + "";
                    if (Zaxis.Min >= 0) zaxis_label = "0";
                    SizeF zsizef = g.MeasureString(zaxis_label, this.font);
                    g.DrawString(zaxis_label, this.font, Brushes.Black, label_pts[10].X - zsizef.Width / 2, label_pts[10].Y + zsizef.Height / 2);

                    zaxis_label = Zaxis.ScaleFormat.ToString(); //Zaxis.Max + "";
                    zsizef = g.MeasureString(zaxis_label, this.font);
                    g.DrawString(zaxis_label, this.font, Brushes.Black, label_pts[11].X - zsizef.Width / 2, label_pts[11].Y + zsizef.Height / 2);
                
                }

                if (Xaxis.IsVisable)
                {
                    Point2D p1 = viewPort.Camera.GetProjection(new Vertex(0, -d, 0));
                    Point2D p2 = viewPort.Camera.GetProjection(new Vertex(0, -d, 0).Offset(0, -d_scale, -d_scale));
                    SizeF zsizef = g.MeasureString(Xaxis.Label + " " + Xaxis.Unit, this.font); //Xaxis.Label + Xaxis.Unit
                    //g.DrawLine(scale_pen, p1.X, p1.Y, p2.X, p2.Y);
                    g.DrawString(Xaxis.Label + " " + Xaxis.Unit, this.font, Brushes.Black, p2.X - zsizef.Width / 2, p2.Y + zsizef.Height / 2);
                }


                if (Yaxis.IsVisable)
                {
                    Point2D p1 = viewPort.Camera.GetProjection(new Vertex(-d, -d, d / 2));
                    Point2D p2 = viewPort.Camera.GetProjection(new Vertex(-d, -d, d / 2).Offset(-d_scale, -d_scale, 0));
                    SizeF zsizef = g.MeasureString(Yaxis.Label + " " + Yaxis.Unit, this.font);
                    //g.DrawLine(scale_pen, p1.X, p1.Y, p2.X, p2.Y);
                    g.DrawString(Yaxis.Label + " " + Yaxis.Unit, this.font, Brushes.Black, p2.X - zsizef.Width / 2, p2.Y - zsizef.Height / 2);
                }

                if (Zaxis.IsVisable)
                {
                    Point2D p1 = viewPort.Camera.GetProjection(new Vertex(d, 0, 0));
                    Point2D p2 = viewPort.Camera.GetProjection(new Vertex(d, 0, 0).Offset(d_scale, 0, -d_scale));
                    SizeF zsizef = g.MeasureString(Zaxis.Label + " " + Zaxis.Unit, this.font);
                    //g.DrawLine(scale_pen, p1.X, p1.Y, p2.X, p2.Y);
                    g.DrawString(Zaxis.Label + " " + Zaxis.Unit, this.font, Brushes.Black, p2.X - zsizef.Width / 2, p2.Y + zsizef.Height / 2);
                }                

            }         
        }

        private void DrawMaskes(Graphics g)
        {
            Point2D[] pts2d = viewPort.Camera.GetProjection(pts3d);

            PointF[][] face = new PointF[6][];
            face[0] = new PointF[] { pts2d[0].ToPointF(), pts2d[1].ToPointF(), pts2d[2].ToPointF(), pts2d[3].ToPointF() };
            face[1] = new PointF[] { pts2d[5].ToPointF(), pts2d[1].ToPointF(), pts2d[0].ToPointF(), pts2d[4].ToPointF() };
            face[2] = new PointF[] { pts2d[1].ToPointF(), pts2d[5].ToPointF(), pts2d[6].ToPointF(), pts2d[2].ToPointF() };
            face[3] = new PointF[] { pts2d[2].ToPointF(), pts2d[6].ToPointF(), pts2d[7].ToPointF(), pts2d[3].ToPointF() };
            face[4] = new PointF[] { pts2d[3].ToPointF(), pts2d[7].ToPointF(), pts2d[4].ToPointF(), pts2d[0].ToPointF() };
            face[5] = new PointF[] { pts2d[4].ToPointF(), pts2d[7].ToPointF(), pts2d[6].ToPointF(), pts2d[5].ToPointF() };

            if (fillingFace)
            {
                using (var brush = new SolidBrush(SystemColors.ControlDark))
                {
                    for (int i = 3; i < 6; i++)
                    {
                        if (Vector2D.IsClockwise(face[i][0], face[i][1], face[i][2])) // the face can be seen by camera
                        {
                            g.FillPolygon(brush, face[i]);
                        }
                    }
                }
            }             

        }

        private void DrawVisable(Graphics g)
        {
            DrawScales(g);
            DrawFaces(g);
        }

        private void DrawInvisible(Graphics g)
        {
            DrawMaskes(g);
        }

        private void DrawLegend(Graphics g)
        {
            if (!string.IsNullOrEmpty(Xaxis.Label) ||
                !string.IsNullOrEmpty(Yaxis.Label) ||
                !string.IsNullOrEmpty(Zaxis.Label))
            {
                Rectangle LegendRect = new Rectangle(this.viewPort.Chart.Width - 206, this.viewPort.Chart.Height - 24, 200, 20);
                g.FillRectangle(new SolidBrush(Color.WhiteSmoke), LegendRect);
                g.DrawString("数据单位 K:千 M:百万 B:十亿", this.font, Brushes.Black, LegendRect, legendStringFormat);
                g.DrawRectangle(new Pen(SystemColors.ControlLight), LegendRect);
            }          
        }

        internal void DrawEnd(Graphics g)
        {
            DrawInvisible(g);
            DrawLegend(g);          
        }

        internal void DrawBegin(Graphics g)
        {
            DrawVisable(g);
        }

    }    

    public class Axis
    {
        public string Unit { get; set; }
        public double Max { get; set; }
        public double Min { get; set; }
        public bool IsVisable { get; set; }
        public int Dimensionality { get; set; }
        public string Label { get; set; }              
        public string Format(string format)
        {            
            return "(" + Min.ToString(format) + "," + Max.ToString(format) + ")";
        }
        public override string ToString()
        {
            return Format("");
        }
        public double ScaleFormat
        {
            get
            {
                double distance = Distance;
                if (Distance > 1000000000) // 亿
                {
                    distance = Math.Round((distance / 1000000000), 2);
                    Label = "B";
                }
                else if (Distance > 1000000) //百万
                {
                    distance = Math.Round((distance / 1000000), 2);
                    Label = "M";
                }
                else if (Distance > 1000) //千
                {
                    distance = Math.Round((distance / 1000), 2);
                    Label = "K";
                }
                else
                {
                    distance = Math.Round(distance, 2);
                }
                return distance;
            }
        }
        internal double Padding { get; set; }
        internal double Distance
        {
            get
            {
                double distance = Math.Max(Math.Abs(Min), Math.Abs(Max)) + Padding;               
                return distance;
            }
        }
        public Axis()
        {
            this.Padding = 0;
            this.IsVisable = true;           
        }

    }    

}
